﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace ReplaceCsv.WallLogParser
{
    internal class WallLogAnalyzeLogic
    {
        /// <summary>
        /// 入力ログのカラムフォーマット
        /// </summary>
        public enum LogFormat:int{
            Year = 0,
            Month = 1,
            Day = 2,
            Hour = 3,
            Minute = 4,
            Second = 5,
            Content = 6
        }
        private TimeSpan _limitTimeSecond;
        private List<CategoryInfomationDef> categoryList = new List<CategoryInfomationDef>();
        /// <summary>
        /// ＷＡＬＬとファイル名（日付）をキーに、そのファイルのすべてのデータを抽出する
        /// </summary>
        private Dictionary<string,Dictionary<string,List<WallLogBean>>> _dataList = new Dictionary<string,Dictionary<string,List<WallLogBean>>>();
        internal Dictionary<string,Dictionary<string,List<WallLogBean>>> dataList{
            get { return _dataList ; }
        }
        internal string[] getSetBaseCategoryNameList()
        {
            List<string> result = new List<string>();
            foreach(CategoryInfomationDef g in categoryList)
            {
                result.Add(g.categoryName);
            }
            return result.ToArray();
        }
        #region |20130326 update コンテンツ名の処理対象取得フィルター|
        //20130326 チェック処理用のデリゲード
        delegate bool checkOutput(WallLogBean bean);
        /// <summary>
        /// グラフ処理対象とするコンテンツ名を取得する。（コンテンツ別滞在時間分布用）
        /// </summary>
        /// <returns></returns>
        internal List<string> getOntimeCategoryContensList()
        {
            return getContentsList(new checkOutput(this.checkOutputedOntimeCategory));
        }
        /// <summary>
        /// グラフ処理対象とするコンテンツ名を取得する。（平均滞在時間用）
        /// </summary>
        /// <returns></returns>
        internal List<string> getAvgContentsList()
        {
            return getContentsList(new checkOutput(this.checkOutputedAvg));
        }
        /// <summary>
        /// グラフ処理対象とするコンテンツ名を取得する。全対象。。実際には利用しない
        /// </summary>
        /// <returns></returns>
        internal List<string> getContentsList()
        {
            return getContentsList(new checkOutput(this.checkOutputedAlways));
        }

        //20130313 コンテンツ名のリストを抽出して返却
        //チェック処理をデリゲード化して変更
        private List<string> getContentsList(checkOutput isOutputedBean)
        {
            List<string> result = new List<string>();
            Dictionary<string,bool> contents = new Dictionary<string,bool>();
            foreach (KeyValuePair<string, Dictionary<string, List<WallLogBean>>> wallLists in this.dataList)
            {
                foreach (KeyValuePair<string, List<WallLogBean>> fileLists in wallLists.Value)
                {
                    foreach (WallLogBean bean in fileLists.Value)
                    {
                        if (bean != null)
                        {
                            //出力対象かどうかを追加する 20130326
                            if (!isOutputedBean(bean)) continue ;
                            //ここで設定するのはbean側である
                            string contentName = bean.pageName;
                            if (!contents.ContainsKey(contentName)) contents.Add(contentName, true);
                        }
                    }
                }
            }
            //Listに展開する
            SortedList<string,string> mySL = new SortedList<string,string>();
            foreach(KeyValuePair<string,bool> x in contents)
            {
                mySL.Add(x.Key,x.Key);
            }
            foreach (KeyValuePair<string,string> de in mySL) 
            {
                result.Add(de.Value);
            }
            return result;
        }
        #region | delegate checkOutputed | 
        //20130326 コンテンツ名のリストを抽出して返却する場合、beanの状態をみて処理するデリゲードメソッド
        private bool checkOutputedOntimeCategory(WallLogBean bean)
        {
            return bean.isOutputedOntimeCategory;
        }
        private bool checkOutputedAvg(WallLogBean bean)
        {
            return bean.isOutputedAvg;
        }
        private bool checkOutputedAlways(WallLogBean bean)
        {
            return true;
        }
        #endregion

        #endregion |20130326 update コンテンツ名の処理対象取得フィルター|

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="limitTimeSecond">アクセスログを計測する最大アクセス時間の初期設定</param>
        public WallLogAnalyzeLogic(double limitTimeSecond)
        {
            //ログの最大アクセス間隔を初期化
            this._limitTimeSecond = TimeSpan.FromSeconds(limitTimeSecond);
        }
        /// <summary>
        /// アクセス時間のカテゴリ情報を追加する。
        /// </summary>
        /// <param name="categoryName">出力（表示）するカテゴリー名</param>
        /// <param name="fromSecond">カテゴリーの属する開始秒（含まない）</param>
        /// <param name="toSecond">カテゴリーの属する終了秒（含む）</param>
        public void addCategoryInfomationDef(string categoryName, double fromSecond, double toSecond){
            categoryList.Add(new CategoryInfomationDef(categoryName, fromSecond, toSecond,true,true));
        }
        /// <summary>
        /// アクセス時間のカテゴリ情報を追加する。最大値用
        /// </summary>
        /// <param name="categoryName">出力（表示）するカテゴリー名</param>
        /// <param name="fromSecond">カテゴリーの属する開始秒（含まない）</param>
        public void addCategoryInfomationDef(string categoryName, double fromSecond)
        {
            //20130322 最大値は滞在時間のログにに出力しない仕様に変更する
            //categoryList.Add(new CategoryInfomationDef(categoryName, fromSecond, -1d,true,false));
            categoryList.Add(new CategoryInfomationDef(categoryName, fromSecond, -1d, false, false));
        }
        /// <summary>
        /// ファイル名からWALL名を取得する
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>
        public string getWallName(string filename)
        {
            Regex matcher = new Regex("(wall)_(\\d)_log_");
            Match m = matcher.Match(filename);
            if (m != null)
            {
                return (m.Groups[1].Value + m.Groups[2].Value).ToUpper();
            }
            else
            {
                return "WALL0";
            }
        }
        /// <summary>
        /// rowデータを解析して、リストに追加する
        /// </summary>
        /// <param name="row"></param>
        public void AddRow(string[] row,string wall,string filename)
        {
            ////ここから作成
            //まずはアクセス時間を取得
            DateTime accessTime = getAccessTime(row[(int)LogFormat.Year],row[(int)LogFormat.Month],row[(int)LogFormat.Day],
                                                row[(int)LogFormat.Hour],row[(int)LogFormat.Minute], row[(int)LogFormat.Second]);
            //ページ名とオリジナルページ名を取得
            string originalPageName = row[(int)LogFormat.Content];
            string pageName = getPageName(originalPageName);

            WallLogBean bean = new WallLogBean(accessTime, originalPageName, pageName);
            addCategroyList(wall, filename, bean);
        }
        /// <summary>
        /// WALLごとのログ情報にログを追加する
        /// </summary>
        /// <param name="wall"></param>
        /// <param name="fileName"></param>
        /// <param name="bean"></param>
        private void addCategroyList(string wall, string fileName, WallLogBean bean)
        {
            if(!this._dataList.ContainsKey(wall))
            {
                this._dataList.Add(wall, new Dictionary<string, List<WallLogBean>>());
            }
            Dictionary<string, List<WallLogBean>> fileLogList = this._dataList[wall];
            if (!fileLogList.ContainsKey(fileName))
            {
                fileLogList.Add(fileName, new List<WallLogBean>());
            }
            List<WallLogBean> logList = fileLogList[fileName];
            logList.Add(bean);
        }
        private static Regex PageNameParse = new Regex("^(KW\\d+ +)(.*)$");
        private static string ReplaceString = "$2";
        /// <summary>
        /// ページ名を取得する
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        private string getPageName(string text)
        {
            return PageNameParse.Replace(text, ReplaceString,1);
        }
        /// <summary>
        /// アクセス日時をDateTime型で取得する
        /// </summary>
        /// <param name="year"></param>
        /// <param name="month"></param>
        /// <param name="day"></param>
        /// <param name="hour"></param>
        /// <param name="minute"></param>
        /// <param name="second"></param>
        /// <returns></returns>
        private DateTime getAccessTime(string year, string month, string day, string hour, string minute, string second)
        {
            //フォーマットがおかしいものについてはエラーを出す必要がある
            //エラー表示方法を別途検討
            return new DateTime(getDigit(year),getDigit(month),getDigit(day),
                                           getDigit(hour),getDigit(minute),getDigit(second));
        }
        private static Regex DigitParse = new Regex("(\\d+)");
        /// <summary>
        /// xxx年、xx月、xx日、xx時、xx分、xx秒を変換して数値に変更
        /// </summary>
        /// <param name="digit"></param>
        /// <returns></returns>
        private int getDigit(string digit)
        {
            int result = 0;
            Match m = DigitParse.Match(digit);
            if (m != null)
            {
                int.TryParse(m.Groups[1].Value, out result);
            }
            return result;
        }
        /// <summary>
        /// ため込んだデータを元に滞在時間を解析する
        /// </summary>
        public void AnalyzeOnTimeFromDatas()
        {
            foreach (KeyValuePair<string, Dictionary<string, List<WallLogBean>>> wallLists in this.dataList)
            {
                foreach (KeyValuePair<string, List<WallLogBean>> fileLists in wallLists.Value)
                {
                    WallLogBean bean = null;
                    foreach (WallLogBean next in fileLists.Value)
                    {
                        if (bean != null)
                        {
                            //ここで設定するのはbean側である
                            setCategoryInfomation(bean, next);
                        }
                        bean = next;
                    }
                    //最後のbeanの処理を今一度追加すること
                    //
                    setCategoryInfomation(bean, null);
                }
            }
        }
        private static DateTime dumyyAccessTime = new DateTime(); 
        private void setCategoryInfomation(WallLogBean prev, WallLogBean next)
        {
            DateTime prevAccessTime = prev.accessDateTime;
            DateTime nextAccessTime = dumyyAccessTime;
            bool nextFlg = false;
            if (next != null)
            {
                nextAccessTime = next.accessDateTime;
                nextFlg = true;
            }
            CategoryInfomation ci = null;
            foreach (CategoryInfomationDef category in categoryList)
            {
                ci = category.isMyCategory(prevAccessTime, nextAccessTime, nextFlg , this._limitTimeSecond);
                if (ci != null) break;
            }
            if (ci != null)
            {
                prev.OnTime = ci.onTime;
                prev.timeCategory = ci.categoryName;
                //ログ出力のフラグを追加する
                prev.isOutputedOntimeCategory = ci.isOutputedOntimeCategory;
                prev.isOutputedAvg = ci.isOutputedAvg;
            }
        }
        /// <summary>
        /// カテゴリー情報の定義を行うクラス
        /// このクラスのisMyCategoryメソッドを通じて
        /// 対象の時間範囲を確認する。
        /// </summary>
        internal class CategoryInfomationDef
        {
            private string _categoryName;
            internal string categoryName
            {
                get { return _categoryName; }
            }
            private TimeSpan _fromSecond;
            internal TimeSpan fromSecond
            {
                get { return _fromSecond; }
            }
            private TimeSpan _toSecond;
            internal TimeSpan toSecond
            {
                get { return _toSecond; }
            }
            /// <summary>
            /// 滞在時間（カテゴリー単位）のログに出力するかどうか
            /// </summary>
            private bool _isOutputedOntimeCategory = false;
            /// <summary>
            /// 滞在時間（カテゴリー単位）のログに出力するかどうか
            /// </summary>
            internal bool isOutputedOntimeCategory
            {
                get { return _isOutputedOntimeCategory; }
            }
            /// <summary>
            /// 平均滞在時間のログに出力するかどうか
            /// </summary>
            private bool _isOutputedAvg = false;
            /// <summary>
            /// 滞在時間（カテゴリー単位）のログに出力するかどうか
            /// </summary>
            internal bool isOutputedAvg
            {
                get { return _isOutputedAvg; }
            }

            private bool isMax = false;
            /// <summary>
            /// カテゴリー情報の種類
            /// </summary>
            /// <param name="categoryName">出力（表示）するカテゴリー名</param>
            /// <param name="fromSecond">カテゴリーの属する開始秒（含まない）</param>
            /// <param name="toSecond">カテゴリーの属する終了秒（含む）</param>
            internal CategoryInfomationDef(string categoryName, double froms, double tos , bool isOntimeCategory,bool isAvg)
            {
                //本当はnullチェックすべきだがいったん何もしない
                this._categoryName = categoryName;
                this._fromSecond = TimeSpan.FromSeconds(froms);
                //tosがマイナス値の場合は最大値とみなして設定
                if (tos < 0d)
                {
                    this.isMax = true;
                }
                else
                {
                    this._toSecond = TimeSpan.FromSeconds(tos);
                }
                this._isOutputedOntimeCategory = isOntimeCategory;
                this._isOutputedAvg = isAvg;
            }
            /// <summary>
            /// 初期値設定
            /// </summary>
            private static TimeSpan initialInterval =  TimeSpan.FromSeconds(0);
            /// <summary>
            /// カテゴリーに属するかどうかをチェックする
            /// </summary>
            /// <param name="prevLogAccessTime">アクセス時間</param>
            /// <param name="nextLogAccessTime">次のアクセス時間</param>
            /// <param name="nextFlg">nextLogAccessTimeが有効かどうか（使用するかどうか）</param>
            /// <param name="limitTime">制限時間（秒）</param>
            /// <returns></returns>
            internal CategoryInfomation isMyCategory(DateTime prevLogAccessTime, DateTime nextLogAccessTime, bool nextFlg , TimeSpan limitTime)
            {
                //20130308 なんかロジックがごちゃごちゃになってしまっているが
                //一旦はこのままとする。
                TimeSpan interval = initialInterval;

                //修正　次のログ時間を使用しない場合intervalは0秒
                //次のログ時間を使用しない場合nullを返却（カテゴリー対象としない）
                if (!nextFlg) return null;

                interval = nextLogAccessTime.Subtract(prevLogAccessTime);
                //ログの間隔がリミット時間よりも大きい場合、最大カテゴリーのデータに対応する
                //if (interval.CompareTo(limitTime) > 0) //20130324 最大値の値を含む形で修正　3分以上⇒3分を含む
                if (interval.CompareTo(limitTime) >= 0)
                {
                        //interval = initialInterval;
                    if (this.isMax)
                    {
                        return new CategoryInfomation(categoryName, interval,false,this.isOutputedOntimeCategory,this.isOutputedAvg);
                    }
                    else
                    {
                        return null;
                    }
                }
                //間隔をチェックし、trueだった場合のみ、CategoryInfomationのインスタンスを生成し返却
                //if (fromSecond.CompareTo(interval) < 0 && toSecond.CompareTo(interval) >= 1) 20130324 バグ修正　
                if (fromSecond.CompareTo(interval) <= 0 && toSecond.CompareTo(interval) >= 1)
                {
                    return new CategoryInfomation(categoryName, interval, true, this.isOutputedOntimeCategory, this.isOutputedAvg);
                }
                return null;
            }
        }
        /// <summary>
        /// カテゴリー情報
        /// </summary>
        internal class CategoryInfomation
        {
            private string _categoryName;
            public string categoryName
            {
                get { return this._categoryName; }
            }
            private TimeSpan _onTime;
            public TimeSpan onTime
            {
                get { return this._onTime;}
            }
            private bool _isCalcObject = true;
            public bool isCalcObject
            {
                get { return this._isCalcObject; }
            }
            /// <summary>
            /// 滞在時間（カテゴリー単位）のログに出力するかどうか
            /// </summary>
            private bool _isOutputedOntimeCategory = false;
            /// <summary>
            /// 滞在時間（カテゴリー単位）のログに出力するかどうか
            /// </summary>
            internal bool isOutputedOntimeCategory
            {
                get { return _isOutputedOntimeCategory; }
            }
            /// <summary>
            /// 平均滞在時間のログに出力するかどうか
            /// </summary>
            private bool _isOutputedAvg = false;
            /// <summary>
            /// 滞在時間（カテゴリー単位）のログに出力するかどうか
            /// </summary>
            internal bool isOutputedAvg
            {
                get { return _isOutputedAvg; }
            }

            /// <summary>
            /// カテゴリー情報生成
            /// </summary>
            /// <param name="categoryName"></param>
            /// <param name="onTime"></param>
            /// <param name="isCalcObject">計算対象のオブジェクトとなるかどうか</param>
            internal CategoryInfomation(string categoryName, TimeSpan onTime ,bool isCalcObject,bool isOntimeCategory,bool isAvg)
            {
                this._categoryName = categoryName;
                this._onTime = onTime;
                this._isOutputedOntimeCategory = isOntimeCategory;
                this._isOutputedAvg = isAvg;
            }
        }
    }
}
